
#pragma once

#ifndef _fmbMutex
#define _fmbMutex

#include "fmbErr.h"

#ifdef _WIN32

#include <process.h>
#include <Windows.h>

#else

#include <pthread.h>

#endif

namespace fm {
  namespace base {

class WaitCondition;

/// <summary>
///   Mutex protects the memory area shared between threads.
///   The mutex works only within threads of single process.
/// </summary>
class Mutex {
  friend class WaitCondition;
private:
#ifdef _WIN32
  typedef CRITICAL_SECTION Handler;

#else
  typedef pthread_mutex_t Handler;
  pthread_mutexattr_t m_attributes;
#endif

  Handler m_handler;
  unsigned int m_level;

  // prevent misuse
  Mutex(const Mutex& source);
  Mutex operator=(const Mutex& source);
  
public:

#ifdef _WIN32

  inline Mutex() : m_level(0) { InitializeCriticalSection(&m_handler); }
  inline ~Mutex() { DeleteCriticalSection(&m_handler); }
  inline void lock() {
    EnterCriticalSection(&m_handler);
    ++m_level;
  }
  inline void unlock() {
    --m_level;
    LeaveCriticalSection(&m_handler);
  }

#else

  inline Mutex() : m_level(0) {
    pthread_mutexattr_init(&m_attributes);
    pthread_mutexattr_settype(&m_attributes, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&m_handler, &m_attributes);
  }
  inline ~Mutex() {
    pthread_mutex_destroy(&m_handler);
    pthread_mutexattr_destroy(&m_attributes);
  }
  inline void lock() {
    pthread_mutex_lock(&m_handler);
    ++m_level;
  }
  inline void unlock() {
    --m_level;
    pthread_mutex_unlock(&m_handler);
  }

#endif

};

class WaitCondition {
private:
#ifdef _WIN32
  typedef CONDITION_VARIABLE Handler;
#else
  typedef pthread_cond_t Handler;
#endif

  Handler m_handler;

  // prevent misuse
  WaitCondition(const WaitCondition& source);
  WaitCondition operator=(const WaitCondition& source);

public:

#ifdef _WIN32

  inline WaitCondition() { InitializeConditionVariable(&m_handler); }
  inline ~WaitCondition() { }

  inline void wait(Mutex &mutex) { 
    unsigned int level = mutex.m_level;
    while (mutex.m_level > 1) mutex.unlock();
    mutex.m_level = 0;
    if (!SleepConditionVariableCS(&m_handler, &(mutex.m_handler), INFINITE))
      throw Err("Error during WaitCondition::sleep");
    mutex.m_level = level == 0 ? 0 : 1;
    while (mutex.m_level < level) mutex.lock();
  }

  inline void signal() {
    WakeConditionVariable(&m_handler);
  }

  inline void signalAll() {
    WakeAllConditionVariable(&m_handler);
  }

#else

  inline WaitCondition() { pthread_cond_init(&m_handler, NULL); }
  inline ~WaitCondition() { pthread_cond_destroy(&m_handler); }

  inline void wait(Mutex &mutex) {
    unsigned int level = mutex.m_level;
    while (mutex.m_level > 1) mutex.unlock();
    mutex.m_level = 0;
    pthread_cond_wait(&m_handler, &(mutex.m_handler));
    mutex.m_level = level == 0 ? 0 : 1;
    while (mutex.m_level < level) mutex.lock();
  }

  inline void signal() {
    pthread_cond_signal(&m_handler);
  }

  inline void signalAll() {
    pthread_cond_broadcast(&m_handler);
  }

#endif

};

/// <summary>
///   Class connected with Mutex.
///   Provides automatically locking and unlocking during constructor and destructor.
/// </summary>
class Locker {
private:
  Mutex *m_mutex;
  inline void lock() { m_mutex->lock(); }
  inline void unlock() { m_mutex->unlock(); }

public:

  inline Locker(Mutex &mutex) : m_mutex(&mutex) { lock(); }
  inline Locker(const Locker& source) : m_mutex(source.m_mutex) { lock(); }
  inline Locker& operator=(const Locker& source) {
    if (&source != this) {
      this->~Locker();
      m_mutex = source.m_mutex;
      lock();
    }
    return *this;
  }
  inline ~Locker() { unlock(); }

};

class SingleThreadLockingPolicy {
public:
  class Lock {
  public:
    inline Lock(SingleThreadLockingPolicy &obj) {}
  };
};

class ObjectLevelLockingPolicy {
  Mutex m_mutex;
public:
  inline Mutex& mutex() { return m_mutex; }
  class Lock {
    Locker locker;
  public:
    inline Lock(ObjectLevelLockingPolicy &obj) : locker(obj.mutex()) {}
  };
};

  // end of namespace fm::base
  }
// end of namespace fm
}

#endif
